Skip to content

Conversation

@kirklandsign
Copy link
Contributor

Summary

Add java binding for extension/asr/runner/runner.h

Test plan

CI

@pytorch-bot
Copy link

pytorch-bot bot commented Jan 28, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/16979

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 3 New Failures, 49 Cancelled Jobs, 2 Pending, 2 Unrelated Failures

As of commit 4ca0b92 with merge base e4060ee (image):

NEW FAILURES - The following jobs have failed:

CANCELLED JOBS - The following jobs were cancelled. Please retry:

FLAKY - The following jobs failed but were likely due to flakiness present on trunk:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jan 28, 2026
@kirklandsign kirklandsign marked this pull request as ready for review January 28, 2026 20:11
Copilot AI review requested due to automatic review settings January 28, 2026 20:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Java/Kotlin bindings for the ExecuTorch ASR (Automatic Speech Recognition) module, enabling Android applications to use ASR models like Whisper. The implementation follows a similar pattern to the existing LLM module bindings.

Changes:

  • Added JNI layer implementation (jni_layer_asr.cpp) to bridge C++ ASR runner with Java/Kotlin
  • Created Kotlin API classes: AsrModule, AsrCallback, and AsrTranscribeConfig for Android integration
  • Updated build scripts and CMake configuration to include ASR runner support

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
scripts/build_android_library.sh Adds EXECUTORCH_BUILD_EXTENSION_ASR_RUNNER flag to Android build configuration
extension/android/jni/jni_layer_asr.cpp Implements JNI bindings for ASR runner including native methods and callback handling
extension/android/executorch_android/src/main/java/org/pytorch/executorch/extension/asr/AsrTranscribeConfig.kt Defines configuration data class for ASR transcription parameters
extension/android/executorch_android/src/main/java/org/pytorch/executorch/extension/asr/AsrModule.kt Main Kotlin API class for ASR module with transcription methods
extension/android/executorch_android/src/main/java/org/pytorch/executorch/extension/asr/AsrCallback.kt Callback interface for receiving transcription tokens and completion events
extension/android/CMakeLists.txt Updates CMake to include ASR JNI layer in the build

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Use int64_t arithmetic to detect overflow when computing
batchSize * timeSteps * featureDim before casting to jsize.
This prevents silent overflow that could cause incorrect
validation and potential out-of-bounds memory access.
Copilot AI review requested due to automatic review settings January 28, 2026 22:26
@kirklandsign kirklandsign added the release notes: android Android Java and JNI code label Jan 28, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 310 to 333
if (scopedCallback) {
initCallbackCache(env);

jobject callbackRef = scopedCallback.get();
tokenCallback = [env, callbackRef, &tokenBuffer](const std::string& token) {
tokenBuffer += token;
if (!utf8_check_validity(tokenBuffer.c_str(), tokenBuffer.size())) {
ET_LOG(
Info, "Current token buffer is not valid UTF-8. Waiting for more.");
return;
}

std::string completeToken = tokenBuffer;
tokenBuffer.clear();

jstring jToken = env->NewStringUTF(completeToken.c_str());
env->CallVoidMethod(callbackRef, callbackCache.onTokenMethod, jToken);
if (env->ExceptionCheck()) {
ET_LOG(Error, "Exception occurred in AsrCallback.onToken");
env->ExceptionClear();
}
env->DeleteLocalRef(jToken);
};
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After calling initCallbackCache, there's no validation that the callback cache was properly initialized. If initCallbackCache failed to find the class or methods, callbackCache.onTokenMethod and callbackCache.onCompleteMethod will be nullptr, causing crashes at lines 326 and 342. Add validation checks after initCallbackCache to ensure the cache is properly initialized before using the callback.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +37
// Helper to get a string from jstring
std::string jstringToString(JNIEnv* env, jstring jstr) {
if (jstr == nullptr) {
return "";
}
const char* chars = env->GetStringUTFChars(jstr, nullptr);
std::string result(chars);
env->ReleaseStringUTFChars(jstr, chars);
return result;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The jstringToString helper function is a simple utility that could potentially be shared across JNI layers. Consider adding it to jni/jni_helper.h if it's used in multiple places. However, this is not critical given its simplicity.

Copilot uses AI. Check for mistakes.
Comment on lines 313 to 332
jobject callbackRef = scopedCallback.get();
tokenCallback = [env, callbackRef, &tokenBuffer](const std::string& token) {
tokenBuffer += token;
if (!utf8_check_validity(tokenBuffer.c_str(), tokenBuffer.size())) {
ET_LOG(
Info, "Current token buffer is not valid UTF-8. Waiting for more.");
return;
}

std::string completeToken = tokenBuffer;
tokenBuffer.clear();

jstring jToken = env->NewStringUTF(completeToken.c_str());
env->CallVoidMethod(callbackRef, callbackCache.onTokenMethod, jToken);
if (env->ExceptionCheck()) {
ET_LOG(Error, "Exception occurred in AsrCallback.onToken");
env->ExceptionClear();
}
env->DeleteLocalRef(jToken);
};
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lambda captures JNIEnv* by value, which is only safe if the callback is guaranteed to be invoked synchronously on the same thread. JNIEnv* pointers are thread-local in JNI and cannot be safely used across threads. While the current runner implementation appears to invoke callbacks synchronously, this creates a fragile dependency. Consider documenting this requirement explicitly or adding a runtime check to verify thread safety.

Suggested change
jobject callbackRef = scopedCallback.get();
tokenCallback = [env, callbackRef, &tokenBuffer](const std::string& token) {
tokenBuffer += token;
if (!utf8_check_validity(tokenBuffer.c_str(), tokenBuffer.size())) {
ET_LOG(
Info, "Current token buffer is not valid UTF-8. Waiting for more.");
return;
}
std::string completeToken = tokenBuffer;
tokenBuffer.clear();
jstring jToken = env->NewStringUTF(completeToken.c_str());
env->CallVoidMethod(callbackRef, callbackCache.onTokenMethod, jToken);
if (env->ExceptionCheck()) {
ET_LOG(Error, "Exception occurred in AsrCallback.onToken");
env->ExceptionClear();
}
env->DeleteLocalRef(jToken);
};
JavaVM* jvm = nullptr;
if (env->GetJavaVM(&jvm) != JNI_OK || jvm == nullptr) {
ET_LOG(Error, "Failed to get JavaVM; token callback will be disabled.");
} else {
jobject callbackRef = scopedCallback.get();
tokenCallback =
[jvm, callbackRef, &tokenBuffer](const std::string& token) {
JNIEnv* envLocal = nullptr;
bool detach = false;
jint getEnvResult =
jvm->GetEnv(reinterpret_cast<void**>(&envLocal), JNI_VERSION_1_6);
if (getEnvResult == JNI_EDETACHED) {
if (jvm->AttachCurrentThread(&envLocal, nullptr) != JNI_OK) {
ET_LOG(
Error,
"Failed to attach current thread to JVM in token callback.");
return;
}
detach = true;
} else if (getEnvResult != JNI_OK || envLocal == nullptr) {
ET_LOG(
Error,
"Failed to get JNIEnv for current thread in token callback.");
return;
}
tokenBuffer += token;
if (!utf8_check_validity(tokenBuffer.c_str(), tokenBuffer.size())) {
ET_LOG(
Info,
"Current token buffer is not valid UTF-8. Waiting for more.");
if (detach) {
jvm->DetachCurrentThread();
}
return;
}
std::string completeToken = tokenBuffer;
tokenBuffer.clear();
jstring jToken = envLocal->NewStringUTF(completeToken.c_str());
envLocal->CallVoidMethod(
callbackRef, callbackCache.onTokenMethod, jToken);
if (envLocal->ExceptionCheck()) {
ET_LOG(Error, "Exception occurred in AsrCallback.onToken");
envLocal->ExceptionClear();
}
envLocal->DeleteLocalRef(jToken);
if (detach) {
jvm->DetachCurrentThread();
}
};
}

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 29, 2026 01:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*
* @param token The decoded text token
*/
fun onToken(token: String)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing @DoNotStrip annotation on the onToken method. This annotation is required for JNI callback methods to prevent ProGuard/R8 from stripping them during release builds. The LlmCallback interface uses this annotation (see extension/android/executorch_android/src/main/java/org/pytorch/executorch/extension/llm/LlmCallback.java:28). You'll need to add the import: import com.facebook.jni.annotations.DoNotStrip

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings January 29, 2026 21:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings January 29, 2026 23:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +112 to +117
auto deleter = [env](jobject ref) {
if (ref != nullptr) {
env->DeleteGlobalRef(ref);
}
};
jobject globalRef = obj ? env->NewGlobalRef(obj) : nullptr;
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleter function captures the JNIEnv pointer by value, which is thread-local and should not be used from the deleter. JNIEnv pointers are thread-specific and cannot be safely used from a different thread context or after the current thread detaches from the JVM.

The deleter should use the JavaVM to get a thread-local JNIEnv when it needs to delete the global reference. Alternatively, since this function is only used within nativeTranscribe and the global reference is deleted before the function returns (while still on the same thread), the deleter could simply be refactored to not capture env at all and instead get it from the JavaVM, or the global reference could be deleted manually before returning instead of using a unique_ptr with a custom deleter.

Suggested change
auto deleter = [env](jobject ref) {
if (ref != nullptr) {
env->DeleteGlobalRef(ref);
}
};
jobject globalRef = obj ? env->NewGlobalRef(obj) : nullptr;
JavaVM* vm = nullptr;
if (env != nullptr) {
jint getVmResult = env->GetJavaVM(&vm);
if (getVmResult != JNI_OK) {
vm = nullptr;
}
}
auto deleter = [vm](jobject ref) {
if (ref == nullptr || vm == nullptr) {
return;
}
JNIEnv* env_local = nullptr;
jint envResult = vm->GetEnv(reinterpret_cast<void**>(&env_local), JNI_VERSION_1_6);
bool didAttach = false;
if (envResult == JNI_EDETACHED) {
if (vm->AttachCurrentThread(&env_local, nullptr) != JNI_OK) {
return;
}
didAttach = true;
} else if (envResult != JNI_OK || env_local == nullptr) {
return;
}
env_local->DeleteGlobalRef(ref);
if (didAttach) {
vm->DetachCurrentThread();
}
};
jobject globalRef = (env != nullptr && obj != nullptr) ? env->NewGlobalRef(obj) : nullptr;

Copilot uses AI. Check for mistakes.
@kirklandsign kirklandsign merged commit d277202 into main Jan 30, 2026
162 of 166 checks passed
@kirklandsign kirklandsign deleted the parakeet branch January 30, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: android Android Java and JNI code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants